/*
*  SkyBufferManager.cpp
*  
*
*  Created by Alexander Trutchenko on 11/02/12.
*  Copyright 2012 Free Team. All rights reserved.
*
*/

#include "SkyBufferManager.h"

#include "SkyOpenALBuffer.h"
#include "SkyTypes.h"
#include "SkyFileUtils.h"
#include "SkyFile.h"
#include "SkyAssert.h"
#include "SkyConfig.h"
#include "SkyDecoder.h"
#include "SkyOpenALInclude.h"

#include <algorithm>

namespace sky {

////////////////////////////////////////////////////////
//
/** SkyBufferManager
*  
*/
////////////////////////////////////////////////////////

class SkySoundFileData : public SkyFileData, public SkyRefCounted
{
friend class SkyBufferManager;

private:
	explicit SkySoundFileData();
	explicit SkySoundFileData(const SkySoundFileData&);
	SkySoundFileData& operator = (const SkySoundFileData&);

private:
	SkySoundFileData(const char* filePath): SkyFileData(filePath) {;}
	~SkySoundFileData(){;}

};


////////////////////////////////////////////////////////
//
/** SkyBufferManager
*  
*/
////////////////////////////////////////////////////////

SkyBufferManager::SkyBufferManager()
: _isOutputDeviceOpened(false)
, _isCurrentContextSet(false)
{
	addDecoders(this);
}

SkyBufferManager::~SkyBufferManager()
{
	for (StaticDecoders::const_iterator it = _decoders.begin(); it != _decoders.end(); ++it)
		delete *it;
	for (CompressedFiles::const_iterator it = _files.begin(); it != _files.end(); ++it)
		delete it->second;
	for (StreamDecodersFactories::const_iterator it = _decoderFactories.begin(); it != _decoderFactories.end(); ++it)
			delete it->second;
	clearAllBuffers();
}

SkyFileData* SkyBufferManager::preloadCompressedFile(const char* file)
{
	CompressedFiles::iterator it = _files.find(file);
	if (it == _files.end())
	{
		SkySoundFileData* fileData = new SkySoundFileData(file);
		if (fileData->data() == NULL)
		{
			delete fileData;
			return NULL;
		}
		_files.insert(CompressedFiles::value_type(file, fileData));
		return fileData;
	}
	it->second->increase();
	
	return it->second;
}

void SkyBufferManager::unloadCompressedFile(const char* file)
{
	CompressedFiles::iterator it = _files.find(file);
	if (it != _files.end())
	{
		if (!it->second->decrease())
			_files.erase(it);
	}
}

	/* Create empty buffer */
SkyOpenALBuffer* SkyBufferManager::createStreamBuffer()
{
	SkyOpenALBuffer* bufferInfo = new SkyOpenALBuffer();
	_streamBuffers.push_back(bufferInfo);
	return bufferInfo;
}
	/* Remove empty buffer */
void SkyBufferManager::removeStreamBuffer(SkyOpenALBuffer* buffer)
{
	for (StreamBuffers::const_iterator it = _streamBuffers.begin(); it != _streamBuffers.end(); ++it)
		if (buffer == (*it))
		{
			SkyAssert((*it)->refCounter() == 1, "SkyBufferManager::removeStreamBuffer(): Wrong remove attempt!");
			delete (*it);
			_streamBuffers.erase(it);
			break;
		}
}

	/* Create stream decoder. Caller must delete it when it not needed */
SkyStreamDecoder* SkyBufferManager::decoderForFile(const char* file)
{
	std::string ext = file;
	ext = ext.substr(ext.find_last_of(".") + 1);
	if (ext.empty())
		return NULL;
	std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
	StreamDecodersFactories::const_iterator it = _decoderFactories.find(ext.c_str());
	if (it != _decoderFactories.end())
	{
		return decoderForKey(ext.c_str(), it->second->createStreamFileData(this, file));
	}
	return NULL;
}

SkyStreamDecoder* SkyBufferManager::decoderForKey(const char* key, SkyStreamData* streamData)
{
	std::string skey = key;
	std::transform(skey.begin(), skey.end(), skey.begin(), ::tolower);
	StreamDecodersFactories::const_iterator it = _decoderFactories.find(skey);
	if (it != _decoderFactories.end())
	{
		return it->second->createDecoder(streamData);
	}
	delete streamData;
	return NULL;
}
	/* Add stream decoder factory */
void SkyBufferManager::addStreamDecoderFactoryForKey(SkyStreamDecoderFactory* factory, const char* key)
{
	SkyAssert(factory != NULL && key != NULL, "SkyBufferManager::addStreamDecoderFactoryForKey(): Invalid parameters!");
	std::string skey = key;
	std::transform(skey.begin(), skey.end(), skey.begin(), ::tolower);
	StreamDecodersFactories::const_iterator it = _decoderFactories.find(skey);
	if (it != _decoderFactories.end())
		delete it->second;
	_decoderFactories[skey] = factory;
}

	// returns bufferID
bool SkyBufferManager::preloadFileToBuffer(const char* file)
{
	SkyOpenALBuffer* bufferInfo = NULL;
	StaticBuffers::iterator itBuffer = _staticBuffers.find(file);
	if (itBuffer == _staticBuffers.end())
	{
		SkySoundFileData* fileData = NULL;
		CompressedFiles::iterator itFile = _files.find(file);
		if (itFile != _files.end())
			fileData = itFile->second;
		if (fileData == NULL)
		{
			fileData = new SkySoundFileData(file);
			if (fileData->data() == NULL)
			{
				//File is not exists or can't be open
				delete fileData;
				return false;
			}
		}
		std::string ext = file;
		ext = ext.substr(ext.find_last_of(".") + 1);
		for (StaticDecoders::const_iterator it = _decoders.begin(); it != _decoders.end(); ++it)
		{
			if (!(*it)->canFileBeDecoded(ext, fileData->data(), fileData->size()))
				continue;
			ALuint bufferID = (*it)->getBufferIDFromFileData(*fileData);
			if (bufferID != AL_NONE)
			{
				bufferInfo = new SkyOpenALBuffer(bufferID, file);
				break;
			}

			SkyBufferHeader header(file);
			SkyMemoryWrapper* wrapper = (*it)->getPCMDataFromFileData(*fileData, header);
			if (wrapper != NULL)
			{
				if (wrapper->data() != NULL)
				{
					bufferInfo = new SkyOpenALBuffer();
					bufferInfo->setData(wrapper->data(), header);
					delete wrapper;
					break;
				}
				delete wrapper;
			}
		}
		
		if (bufferInfo != NULL)
			_staticBuffers.insert(StaticBuffers::value_type(file, bufferInfo));
		
		if (itFile == _files.end())
			delete fileData;
	}
	else
	{
		bufferInfo = itBuffer->second;
		bufferInfo->increase();
	}
	return (bufferInfo != NULL);
}

void SkyBufferManager::unloadFileFromBuffer(const char* file)
{
	StaticBuffers::iterator it = _staticBuffers.find(file);
	if (it != _staticBuffers.end())
	{
		if (!it->second->decrease())
			_staticBuffers.erase(file);
	}
}

SkyOpenALBuffer* SkyBufferManager::bufferForFile(const char* file)
{
	StaticBuffers::iterator it = _staticBuffers.find(file);
	if (it != _staticBuffers.end())
		return it->second;
	if (SkyBufferManager::preloadFileToBuffer(file))
	{
		it = _staticBuffers.find(file);
		if (it != _staticBuffers.end())
			return it->second;
	}
	return NULL;
}

void SkyBufferManager::clearAllBuffers()
{
	for (StaticBuffers::const_iterator it = _staticBuffers.begin(); it != _staticBuffers.end(); ++it)
	{
		SkyAssert(it->second->refCounter() == 1, "SkyBufferManager::clearAllBuffers(): Wrong remove attempt!");
		delete it->second;
	}
	_staticBuffers.clear();

	for (StreamBuffers::const_iterator it = _streamBuffers.begin(); it != _streamBuffers.end(); ++it)
	{
		SkyAssert((*it)->refCounter() == 1, "SkyBufferManager::clearAllBuffers(): Wrong remove attempt!");
		delete (*it);
	}
	_streamBuffers.clear();
}

void SkyBufferManager::addStaticDecoder(SkyStaticDecoder* decoder)
{
	SkyAssert(decoder != NULL, "SkyBufferManager::addStaticDecoder(): Decoder must not be NULL!");
	if (decoder->isValid())
		_decoders.push_back(decoder);
	else
		delete decoder; 
}

////////////////////////////////////////////////////////
// Callbacks
void SkyBufferManager::onOpenOutputDevice()
{
	SkyAssert(_isOutputDeviceOpened == false, "SkyBufferManager::onOpenOutputDevice(): device must be closed before opening!");
	_isOutputDeviceOpened = true;
	for (StaticDecoders::const_iterator it = _decoders.begin(); it != _decoders.end(); ++it)
		(*it)->onOpenOutputDevice();
}

void SkyBufferManager::onCloseOutputDevice()
{
	SkyAssert(_isOutputDeviceOpened == true, "SkyBufferManager::onCloseOutputDevice(): device must be opened before close!");
	_isOutputDeviceOpened = false;
	for (StaticDecoders::const_iterator it = _decoders.begin(); it != _decoders.end(); ++it)
		(*it)->onCloseOutputDevice();
}

void SkyBufferManager::onSetCurrentContext()
{
	SkyAssert(!_isCurrentContextSet, "SkyBufferManager::onSetCurrentContext(): context must be closed before opening!");
	_isCurrentContextSet = true;
	for (StaticDecoders::const_iterator it = _decoders.begin(); it != _decoders.end(); ++it)
		(*it)->onSetCurrentContext();
}

void SkyBufferManager::onClearCurrentContext()
{
	SkyAssert(_isCurrentContextSet == true, "SkyBufferManager::onClearCurrentContext(): context must be opened before closing!");
	_isCurrentContextSet = false;
	for (StaticDecoders::const_iterator it = _decoders.begin(); it != _decoders.end(); ++it)
		(*it)->onClearCurrentContext();
}

} // namespace sky
